package game.service.impl;

import cn.hutool.core.util.StrUtil;
import com.baomidou.mybatisplus.extension.conditions.query.LambdaQueryChainWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.google.common.collect.Lists;
import common.config.datasource.DataSourceConstants;
import common.config.exception.CustomException;
import common.model.compare.BaseCompare;
import common.model.enums.ClassificationTypeEnum;
import common.model.po.*;
import common.model.response.base.Result;
import common.service.CommonGClassificationInputIdService;
import common.util.NullChecker;
import game.mapper.GBlueprintClassificationMapper;
import game.mapper.GClassificationMapper;
import game.model.compare.GClassificationCompare;
import game.model.converter.GClassificationConverter;
import game.model.dto.ClassificationTreeDto;
import game.model.request.*;
import game.model.vo.CascadeClassificationVo;
import game.model.vo.ClassificationTreeVo;
import game.service.*;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import javax.annotation.Resource;
import java.util.Comparator;
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;

/**
 * 分类服务实现类
 *
 * @author 米泽鹏
 * @since 2022-02-26
 */
@Service
public class GClassificationServiceImpl extends ServiceImpl<GClassificationMapper, GClassification> implements GClassificationService {

	@Resource
	private GClassificationMapper mapper;
	@Resource
	private GBuildingService buildingService;
	@Resource
	private GItemService itemService;
	@Resource
	private GCreatureService creatureService;
	@Resource
	private GProductService productService;
	@Resource
	private CommonGClassificationInputIdService classificationInputIdService;
	@Resource
	private GBlueprintClassificationService blueprintClassificationService;
	@Resource
	private GBlueprintClassificationMapper blueprintClassificationMapper;

	@Override
	public List<CascadeClassificationVo> getCascadeClassification(ClassificationTypeEnum classificationType) {
		LambdaQueryChainWrapper<GClassification> queryWrapper = lambdaQuery();
		queryWrapper.select(GClassification.class, item -> !DataSourceConstants.CREATE_TIME_FIELD_NAME_IN_DATABASE.equals(item.getColumn()) && !DataSourceConstants.UPDATE_TIME_FIELD_NAME_IN_DATABASE.equals(item.getColumn()));
		queryWrapper.eq(GClassification::getType, classificationType.getValue());
		List<GClassification> poList = queryWrapper.list();
		List<GClassification> poRoots = poList.stream().filter(item -> item.getParentId() == 0).sorted(Comparator.comparingInt(GClassification::getSort)).collect(Collectors.toList());
		List<CascadeClassificationVo> roots = GClassificationConverter.poListToCascadeClassificationVoList(poRoots);
		buildCascadeClassificationRecursion(poList, roots);
		return roots;
	}

	/**
	 * 构建类别的el-cascader级联结构（递归构建下级）
	 */
	private void buildCascadeClassificationRecursion(List<GClassification> poList, List<CascadeClassificationVo> roots) {
		for (CascadeClassificationVo root : roots) {
			List<GClassification> poChildren = poList.stream().filter(item -> item.getParentId().equals(root.getValue())).sorted(Comparator.comparingInt(GClassification::getSort)).collect(Collectors.toList());
			List<CascadeClassificationVo> children = GClassificationConverter.poListToCascadeClassificationVoList(poChildren);
			root.setChildren(children);
			if (children.isEmpty()) {
				root.setChildren(null);
			} else {
				buildCascadeClassificationRecursion(poList, children);
			}
		}
	}

	@Override
	public List<ClassificationTreeVo> getClassificationTree(ClassificationTypeEnum classificationType) {
		List<ClassificationTreeDto> dtoList = mapper.selectClassificationTree(classificationType.getValue());
		List<ClassificationTreeDto> dtoRoots = dtoList.stream().filter(item -> item.getParentId() == 0).sorted(Comparator.comparingInt(ClassificationTreeDto::getSort)).collect(Collectors.toList());
		List<ClassificationTreeVo> roots = GClassificationConverter.dtoListToClassificationTreeVoList(dtoRoots);
		buildClassificationTreeRecursion(dtoList, roots);
		// 处理非叶子节点的count
		roots.forEach(this::fillCount);
		return roots;
	}

	/**
	 * 构建类别的el-tree树结构（递归构建下级）
	 */
	private void buildClassificationTreeRecursion(List<ClassificationTreeDto> dtoList, List<ClassificationTreeVo> roots) {
		for (ClassificationTreeVo root : roots) {
			List<ClassificationTreeDto> dtoChildren = dtoList.stream().filter(dto -> dto.getParentId().equals(root.getId())).sorted(Comparator.comparingInt(ClassificationTreeDto::getSort)).collect(Collectors.toList());
			List<ClassificationTreeVo> children = GClassificationConverter.dtoListToClassificationTreeVoList(dtoChildren);
			root.setChildren(children);
			if (!children.isEmpty()) {
				buildClassificationTreeRecursion(dtoList, children);
			}
		}
	}

	private int fillCount(ClassificationTreeVo root) {
		if (root.getChildren().isEmpty()) {
			return root.getCount();
		}
		int sum = 0;
		for (ClassificationTreeVo child : root.getChildren()) {
			sum += fillCount(child);
		}
		root.setCount(sum);
		return sum;
	}

	@Transactional(rollbackFor = Exception.class)
	@Override
	public Result<?> saveClassification(SaveClassificationRequest request) {
		ClassificationTypeEnum classificationType = request.getType();
		// 入参校验
		List<SaveClassificationRequest.ClassificationInfo> classList = request.getClassList();
		for (SaveClassificationRequest.ClassificationInfo classificationInfo : classList) {
			if (!classificationType.equals(classificationInfo.getType())) {
				throw new CustomException("类别类型不匹配");
			}
		}
		List<GClassificationCompare> currentList = GClassificationConverter.poListToCompareList(lambdaQuery().eq(GClassification::getType, classificationType.getValue()).list());
		List<GClassificationCompare> destinationList = GClassificationConverter.saveRequestListToCompareList(classList);
		List<GClassification> updateList = GClassificationConverter.compareListToPoList(getUpdateList(currentList, destinationList));
		Set<Long> deleteIdSet = getDeleteIdSet(currentList, destinationList);
		// 删除
		if (!deleteIdSet.isEmpty()) {
			List<GClassification> classificationList = listByIds(deleteIdSet);
			List<String> classNames = Lists.newArrayList();
			for (GClassification classification : classificationList) {
				switch (classificationType) {
					case ITEM:
						if (itemService.lambdaQuery().eq(GItem::getClassificationId, classification.getId()).count() > 0) {
							classNames.add(classification.getName());
						}
						break;
					case BUILDING:
						if (buildingService.lambdaQuery().eq(GBuilding::getClassificationId, classification.getId()).count() > 0) {
							classNames.add(classification.getName());
						}
						break;
					case CREATURE:
						if (creatureService.lambdaQuery().eq(GCreature::getClassificationId, classification.getId()).count() > 0) {
							classNames.add(classification.getName());
						}
						break;
					case PRODUCT:
						if (productService.lambdaQuery().eq(GProduct::getClassificationId, classification.getId()).count() > 0) {
							classNames.add(classification.getName());
						}
						break;
					default:
						break;
				}
			}
			if (!classNames.isEmpty()) {
				throw new CustomException(StrUtil.format("这些类别已分配了{}，无法删除：{}", classificationType.getDesc(), StrUtil.join("、", classNames)));
			}
			removeBatchByIds(deleteIdSet);
		}
		// 更新
		updateBatchById(updateList);
		// 撤销删除后需要恢复的数据，重新插入，但带有id
		List<GClassification> insertList = GClassificationConverter.compareListToPoList(getInsertList(currentList, destinationList));
		List<GClassification> undoList = insertList.stream().filter(item -> item.getId() != null).collect(Collectors.toList());
		List<GClassificationInputId> inputIdPoList = GClassificationConverter.poListToInputIdPoList(undoList);
		classificationInputIdService.saveBatch(inputIdPoList);
		// 依次选出parentId不为空的数据插入
		insertList.removeAll(undoList);
		List<GClassification> insertListWithParentId = insertList.stream().filter(po -> po.getParentId() != null).collect(Collectors.toList());
		while (!insertListWithParentId.isEmpty()) {
			for (GClassification classification : insertListWithParentId) {
				classification.insert();
			}
			List<GClassification> insertListWithParentIdTemp = Lists.newArrayList();
			insertListWithParentId.forEach(insertedPo -> insertList.stream().filter(po -> po.getParentTreeNodeId().equals(insertedPo.getTreeNodeId())).forEach(po -> {
				po.setParentId(insertedPo.getId());
				insertListWithParentIdTemp.add(po);
			}));
			insertListWithParentId = insertListWithParentIdTemp;
		}
		return Result.success();
	}

	/**
	 * 获取需要新增的元素的list
	 * ① id是null的数据
	 * ② id不是null且currentList不含此id的数据
	 *
	 * @param currentList     数据库当前的记录
	 * @param destinationList 变更后的记录
	 * @param <T>             T 为 BaseCompare 的子类
	 * @return List<T>
	 */
	private <T extends BaseCompare> List<T> getInsertList(List<T> currentList, List<T> destinationList) {
		if (NullChecker.checkAnyNull(currentList, destinationList)) {
			throw new CustomException("参数不能为空");
		}
		List<T> insertList = Lists.newArrayList();
		destinationList.stream().filter(item -> item.getId() == null).forEach(insertList::add);
		Set<Long> currentIdSet = currentList.stream().map(T::getId).collect(Collectors.toSet());
		insertList.addAll(destinationList.stream().filter(item -> item.getId() != null && !currentIdSet.contains(item.getId())).collect(Collectors.toList()));
		return insertList;
	}

	/**
	 * 获取需要更新的元素的list
	 * id不是null且currentList不含此对象的数据
	 *
	 * @param currentList     数据库当前的记录
	 * @param destinationList 变更后的记录
	 * @param <T>             T 为 BaseCompare 的子类
	 * @return List<T>
	 */
	private <T extends BaseCompare> List<T> getUpdateList(List<T> currentList, List<T> destinationList) {
		if (NullChecker.checkAnyNull(currentList, destinationList)) {
			throw new CustomException("参数不能为空");
		}
		return destinationList.stream().filter(item -> item.getId() != null && !currentList.contains(item)).collect(Collectors.toList());
	}

	/**
	 * 获取需要删除的元素的id的set：
	 * 包含于currentList但不包含于destinationList的数据
	 *
	 * @param currentList     数据库当前的记录
	 * @param destinationList 变更后的记录
	 * @param <T>             T 为 BaseCompare 的子类
	 * @return Set<Long>
	 */
	private <T extends BaseCompare> Set<Long> getDeleteIdSet(List<T> currentList, List<T> destinationList) {
		if (NullChecker.checkAnyNull(currentList, destinationList)) {
			throw new CustomException("参数不能为空");
		}
		Set<Long> destinationIdSet = destinationList.stream().map(T::getId).collect(Collectors.toSet());
		Set<Long> currentIdSet = currentList.stream().map(T::getId).collect(Collectors.toSet());
		return currentIdSet.stream().filter(item -> !destinationIdSet.contains(item)).collect(Collectors.toSet());
	}

	@Override
	@Transactional(rollbackFor = Exception.class)
	public Result<String> saveClassificationBuilding(SaveClassificationBuildingRequest request) {
		Long classificationId = request.getClassificationId();
		List<Long> buildingIdListOld = request.getBuildingIdListOld();
		List<Long> buildingIdList = request.getBuildingIdList();
		Boolean prompt = request.getPrompt();
		GClassification classification = getById(classificationId);
		// 更新类别id和同级排序
		if (!buildingIdList.isEmpty()) {
			String last = "order by field(id," + StrUtil.join(",", buildingIdList) + ")";
			List<GBuilding> buildingList = buildingService.lambdaQuery().in(GBuilding::getId, buildingIdList).last(last).list();
			List<GBuilding> buildingChangedClassList = Lists.newArrayList();
			for (GBuilding building : buildingList) {
				if (building.getClassificationId() != null && !building.getClassificationId().equals(classificationId)) {
					buildingChangedClassList.add(building);
				}
				building.setClassificationId(classificationId);
				building.setSort(buildingList.indexOf(building) + 1);
			}
			if (!buildingChangedClassList.isEmpty()) {
				if (prompt) {
					return Result.successWithData(StrUtil.format("以下建筑已有其他分类，如果继续，其分类将会被更改为【{}】：{}", classification.getName(), StrUtil.join("、", buildingChangedClassList.stream().map(GBuilding::getBuildingName).collect(Collectors.toList()))));
				}
			}
			buildingService.updateBatchById(buildingList);
		}
		// 从该类别删除的建筑id
		Set<Long> deleteBuildingIdSet = buildingIdListOld.stream().filter(id -> !buildingIdList.contains(id)).collect(Collectors.toSet());
		if (!deleteBuildingIdSet.isEmpty()) {
			buildingService.lambdaUpdate().in(GBuilding::getId, deleteBuildingIdSet).set(GBuilding::getClassificationId, null).set(GBuilding::getSort, 0).update();
		}
		return Result.success();
	}

	@Override
	public Result<String> saveClassificationItem(SaveClassificationItemRequest request) {
		Long classificationId = request.getClassificationId();
		List<Long> itemIdListOld = request.getItemIdListOld();
		List<Long> itemIdList = request.getItemIdList();
		Boolean prompt = request.getPrompt();
		GClassification classification = getById(classificationId);
		// 更新类别id和同级排序
		if (!itemIdList.isEmpty()) {
			String last = "order by field(id," + StrUtil.join(",", itemIdList) + ")";
			List<GItem> itemList = itemService.lambdaQuery().in(GItem::getId, itemIdList).last(last).list();
			List<GItem> itemChangedClassList = Lists.newArrayList();
			for (GItem item : itemList) {
				if (item.getClassificationId() != null && !item.getClassificationId().equals(classificationId)) {
					itemChangedClassList.add(item);
				}
				item.setClassificationId(classificationId);
				item.setSort(itemList.indexOf(item) + 1);
			}
			if (!itemChangedClassList.isEmpty()) {
				if (prompt) {
					return Result.successWithData(StrUtil.format("以下物品已有其他分类，如果继续，其分类将会被更改为【{}】：{}", classification.getName(), StrUtil.join("、", itemChangedClassList.stream().map(GItem::getItemName).collect(Collectors.toList()))));
				}
			}
			itemService.updateBatchById(itemList);
		}
		// 从该类别删除的物品id
		Set<Long> deleteBuildingIdSet = itemIdListOld.stream().filter(id -> !itemIdList.contains(id)).collect(Collectors.toSet());
		if (!deleteBuildingIdSet.isEmpty()) {
			itemService.lambdaUpdate().in(GItem::getId, deleteBuildingIdSet).set(GItem::getClassificationId, null).set(GItem::getSort, 0).update();
		}
		return Result.success();
	}

	@Override
	public Result<String> saveClassificationCreature(SaveClassificationCreatureRequest request) {
		Long classificationId = request.getClassificationId();
		List<Long> creatureIdListOld = request.getCreatureIdListOld();
		List<Long> creatureIdList = request.getCreatureIdList();
		Boolean prompt = request.getPrompt();
		GClassification classification = getById(classificationId);
		// 更新类别id和同级排序
		if (!creatureIdList.isEmpty()) {
			String last = "order by field(id," + StrUtil.join(",", creatureIdList) + ")";
			List<GCreature> creatureList = creatureService.lambdaQuery().in(GCreature::getId, creatureIdList).last(last).list();
			List<GCreature> creatureChangedClassList = Lists.newArrayList();
			for (GCreature creature : creatureList) {
				if (creature.getClassificationId() != null && !creature.getClassificationId().equals(classificationId)) {
					creatureChangedClassList.add(creature);
				}
				creature.setClassificationId(classificationId);
				creature.setSort(creatureList.indexOf(creature) + 1);
			}
			if (!creatureChangedClassList.isEmpty()) {
				if (prompt) {
					return Result.successWithData(StrUtil.format("以下生物已有其他分类，如果继续，其分类将会被更改为【{}】：{}", classification.getName(), StrUtil.join("、", creatureChangedClassList.stream().map(GCreature::getCreatureName).collect(Collectors.toList()))));
				}
			}
			creatureService.updateBatchById(creatureList);
		}
		// 从该类别删除的生物id
		Set<Long> deleteBuildingIdSet = creatureIdListOld.stream().filter(id -> !creatureIdList.contains(id)).collect(Collectors.toSet());
		if (!deleteBuildingIdSet.isEmpty()) {
			creatureService.lambdaUpdate().in(GCreature::getId, deleteBuildingIdSet).set(GCreature::getClassificationId, null).set(GCreature::getSort, 0).update();
		}
		return Result.success();
	}

	@Override
	public Result<String> saveClassificationProduct(SaveClassificationProductRequest request) {
		Long classificationId = request.getClassificationId();
		List<Long> productIdListOld = request.getProductIdListOld();
		List<Long> productIdList = request.getProductIdList();
		Boolean prompt = request.getPrompt();
		GClassification classification = getById(classificationId);
		// 更新类别id和同级排序
		if (!productIdList.isEmpty()) {
			String last = "order by field(id," + StrUtil.join(",", productIdList) + ")";
			List<GProduct> productList = productService.lambdaQuery().in(GProduct::getId, productIdList).last(last).list();
			List<GProduct> productChangedClassList = Lists.newArrayList();
			for (GProduct product : productList) {
				if (product.getClassificationId() != null && !product.getClassificationId().equals(classificationId)) {
					productChangedClassList.add(product);
				}
				product.setClassificationId(classificationId);
				product.setSort(productList.indexOf(product) + 1);
			}
			if (!productChangedClassList.isEmpty()) {
				if (prompt) {
					return Result.successWithData(StrUtil.format("以下商品已有其他分类，如果继续，其分类将会被更改为【{}】：{}", classification.getName(), StrUtil.join("、", productChangedClassList.stream().map(GProduct::getProductName).collect(Collectors.toList()))));
				}
			}
			productService.updateBatchById(productList);
		}
		// 从该类别删除的商品id
		Set<Long> deleteBuildingIdSet = productIdListOld.stream().filter(id -> !productIdList.contains(id)).collect(Collectors.toSet());
		if (!deleteBuildingIdSet.isEmpty()) {
			productService.lambdaUpdate().in(GProduct::getId, deleteBuildingIdSet).set(GProduct::getClassificationId, null).set(GProduct::getSort, 0).update();
		}
		return Result.success();
	}

	@Override
	public Result<String> saveClassificationBlueprint(SaveClassificationBlueprintRequest request) {
		Long classificationId = request.getClassificationId();
		List<Long> blueprintIdList = request.getBlueprintIdList();
		// 删除原有的关联关系
		blueprintClassificationService.lambdaUpdate().eq(GBlueprintClassification::getClassificationId, classificationId).remove();
		// 添加新的关联关系
		if (!blueprintIdList.isEmpty()) {
			List<GBlueprintClassification> blueprintClassificationList = blueprintIdList.stream().map(blueprintId -> {
				GBlueprintClassification blueprintClassification = new GBlueprintClassification();
				blueprintClassification.setBlueprintId(blueprintId);
				blueprintClassification.setClassificationId(classificationId);
				blueprintClassification.setSort(blueprintIdList.indexOf(blueprintId) + 1);
				return blueprintClassification;
			}).collect(Collectors.toList());
			blueprintClassificationService.saveBatch(blueprintClassificationList);
		}
		return Result.success();
	}

	@Override
	public List<GBlueprint> getAllBlueprintByClassId(Long classId) {
		return blueprintClassificationMapper.getAllBlueprintByClassId(classId);
	}

}
